﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLite.Data.Dialect;
using NLite.Data.Dialect.Function;
using NLite.Data.Internal;

namespace NLite.Data
{
    

    public abstract partial class CriterionNode : Node, ICriterionNode
    {
        public CriterionNode(NodeType type) : base(type) { }
        public CriterionNode And(ICriterionNode right)
        {
            if (ReferenceEquals(right, null))
                return this;
            return new AndNode(this, right);
        }

        public CriterionNode Or(ICriterionNode exp)
        {
            if (ReferenceEquals(exp, null))
                return this.Clone() as CriterionNode;
            return new OrExpression(this, exp);
        }
        public CriterionNode Not()
        {
            if (NodeType == NodeType.Not)
            {
                var criterion = (this as NotNode).Expression as CriterionNode;
                if (criterion != null)
                    return criterion;
            }

            return new NotNode(this);
        }

        #region Operators

        public static bool operator true(CriterionNode right)
        {
            return false;
        }

        public static bool operator false(CriterionNode right)
        {
            return false;
        }

       

        public static CriterionNode operator &(CriterionNode left, CriterionNode right)
        {
            if (ReferenceEquals(left, null))
                return right.Clone() as CriterionNode;

            if (ReferenceEquals(right, null))
                return left.Clone() as CriterionNode;

            return new AndNode(left.Clone() as ICriterionNode, right.Clone() as ICriterionNode);
        }

        public static CriterionNode operator |(CriterionNode left, CriterionNode right)
        {
            if (ReferenceEquals(left, null))
                return right.Clone() as CriterionNode;

            if (ReferenceEquals(right, null))
                return left.Clone() as CriterionNode;

            return new OrExpression(left.Clone() as ICriterionNode, right.Clone() as ICriterionNode);
        }

        public static CriterionNode operator &(SqlBoolean left, CriterionNode right)
        {
            if (ReferenceEquals(right, null))
                throw new ArgumentNullException("right");

            if (ReferenceEquals(left, null))
                return right.Clone() as CriterionNode;

            return new AndNode(left.Clone() as ICriterionNode, right.Clone() as ICriterionNode);
        }

        public static CriterionNode operator &(CriterionNode left, SqlBoolean right)
        {
            if (ReferenceEquals(left, null))
                throw new ArgumentNullException("left");

            if (ReferenceEquals(right, null))
                return left.Clone() as CriterionNode;

            return new AndNode(left.Clone() as ICriterionNode, right.Clone() as ICriterionNode);
        }

        public static CriterionNode operator |(SqlBoolean left, CriterionNode right)
        {
            if (ReferenceEquals(right, null))
                throw new ArgumentNullException("right");

            if (ReferenceEquals(left, null))
                return right.Clone() as CriterionNode;

            return new OrExpression(left.Clone() as ICriterionNode, right.Clone() as ICriterionNode);
        }

        public static CriterionNode operator |(CriterionNode left, SqlBoolean right)
        {
            if (ReferenceEquals(left, null))
                throw new ArgumentNullException("left");

            if (ReferenceEquals(right, null))
                return left.Clone() as CriterionNode;

            return new OrExpression(left.Clone() as ICriterionNode, right.Clone() as ICriterionNode);
        }

        public static CriterionNode operator !(CriterionNode exp)
        {
            if (ReferenceEquals(exp, null))
                throw new ArgumentNullException("exp");

            return exp.Not();
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion
    }

    public abstract partial class ContainerNode : CriterionNode
    {
        public LogicNode Criterion { get; internal set; }
        public ContainerNode(NodeType type) : base(type) { }

        protected void AddCriterionCore(ICriterionNode criterion, LogicOperator type)
        {
            if (criterion == null)
                throw new ArgumentNullException("criterion");

            //criterion = criterion.Clone() as ICriterionNode;
            //var tmp = Criterion == null ? null : Criterion.Clone() as ICriterionNode;

            if (type == LogicOperator.And)
                Criterion = new AndNode(Criterion, criterion);
            else
                Criterion = new OrExpression(Criterion, criterion);
        }
    }

    public partial class HavingNode : ContainerNode
    {
        public HavingNode() : base(NodeType.Having) { }
        public HavingNode AddCriterion(ICriterionNode criterion)
        {
            base.AddCriterionCore(criterion, LogicOperator.Or);
            return this;
        }

        public HavingNode AddCriterion(ICriterionNode criterion, LogicOperator type)
        {
            base.AddCriterionCore(criterion, type);
            return this;
        }
    }

    public partial class WhereNode : ContainerNode
    {
        public WhereNode() : base(NodeType.Where) { }
        public WhereNode AddCriterion(ICriterionNode criterion)
        {
            base.AddCriterionCore(criterion, LogicOperator.And);
            return this;
        }

        public WhereNode AddCriterion(ICriterionNode criterion, LogicOperator type)
        {
            base.AddCriterionCore(criterion, type);
            return this;
        }
    }

    public abstract partial class CompositeNode : CriterionNode
    {
        public INode Left { get; internal set; }
        public INode Right { get; internal set; }
        internal CompositeNode(INode left, INode right,NodeType type)
            : base(type)
        {
            Left = left;
            Right = right;
        }

        public bool IsEmpty
        {
            get { return Left == null && Right == null; }
        }
    }

    public partial class CompareNode : CompositeNode
    {
        public readonly CompareOperator Operator;
        internal CompareNode(INode left, CompareOperator op, INode right):base(left,right, NodeType.Compare)
        {
            Operator = op;
        }
    }

    public abstract partial class LogicNode : CompositeNode
    {
        public readonly LogicOperator Operator;
        internal LogicNode(INode left, LogicOperator op, INode right, NodeType type)
            : base(left,right, type)
        {
            Operator = op;
        }
    }

    public partial class AndNode : LogicNode
    {
        public AndNode(ICriterionNode left, ICriterionNode right) : base(left, LogicOperator.And, right, NodeType.And) { }
    }
    public partial class OrExpression : LogicNode
    {
        public OrExpression(ICriterionNode left, ICriterionNode right) : base(left, LogicOperator.Or, right, NodeType.Or) { }
    }

    public partial class NotNode : CriterionNode
    {
        public readonly ICriterionNode Expression;
        public NotNode(ICriterionNode expression):base(NodeType.Not)
        {
            Expression = expression;
        }
    }

    public partial class IsNullNode : CriterionNode
    {
        public INode Value { get; internal set; }

        public IsNullNode(INode value)
            : base(NodeType.IsNull)
        {
            Value = value;
        }
    }

    public partial class IsNullOrEmptyNode : CriterionNode
    {
        public INode Value { get; internal set; }

        public IsNullOrEmptyNode(INode value)
            : base(NodeType.IsNullOrEmpty)
        {
            Value = value;
        }
    }


    public partial class ExistsNode : CriterionNode
    {
        public SelectNode Query { get; internal set; }
        public ExistsNode(SelectNode query):base(NodeType.Exists)
        {
            Query = query;
        }
    }

    public partial class NativeNode : CriterionNode,IProjectionNode
    {
        public string Expression { get; internal set; }

        internal NativeNode(string filter):base(NodeType.Native)
        {
            Expression = filter;
        }

        public AliasNode As(string alias)
        {
           return new AliasNode(this,alias);
        }
    }
  
    public partial class SubQuery : CriterionNode
    {
        public INode Column { get; private set; }
        public IEnumerable<Node> Range { get; private set; }
        public SelectNode Query { get; private set; }
        public SubQueryType Type { get; private set; }

        public SubQuery(INode column, IEnumerable<Node> range, SelectNode query,SubQueryType type)
            : base(NodeType.SubQuery)
        {
            Column = column;
            Range = range;
            Query = query;
            Type = type;
        }
    }

    public partial class BetweenNode : CriterionNode
    {
        public INode Column { get; internal set; }
        public INode Left { get; internal set; }
        public INode Right { get; internal set; }

        public BetweenNode(INode column, INode left, INode right)
            : base(NodeType.Between)
        {
            Column = column;
            Left = left;
            Right = right;
        }
    
    }

    public partial class LikeNode : CriterionNode
    {
        public INode Column { get; internal set; }
        public INode Value { get; internal set; }
        public LikeNode(INode column, INode value)
            : base(NodeType.Like)
        {
            Column = column;
            Value = value;
        }
    }

    public partial class CaseNode : ProjectionNode
    {
        internal List<ICriterionNode> whens;
        internal List<INode> thens;
        internal INode @else;

 
        internal CaseNode() : base(NodeType.Case) { }

        public INode ToFunction()
        {
            return new FunctionExpression(FunctionView.Case
                , new CollectionProxy( whens.Cast<INode>())
                , new CollectionProxy( thens)
                , @else);
        }

        public override object Clone()
        {
            return this;
        }
    }

    public partial class Case
    {
        public interface IWhen
        {
            IThen Then(INode exp);
        }
        public interface IThen
        {
            IWhen When(ICriterionNode exp);
            IElse Else(INode exp);
            CaseNode End { get; }
        }
        public interface IElse
        {
            CaseNode End { get; }
        }
        public static IWhen When(ICriterionNode test)
        {
            var exp = new InternalCase(test);
            return exp as IWhen;
        }
    }

    public static partial class IIFExtensions
    {
        public static CaseNode IIF(this ICriterionNode test, INode @true, INode @false)
        {
            var @case = new InternalCase(test);
            @case.Inner.thens.Add(@true);
            @case.Inner.@else = @false;
            return @case.Inner;
        }
       
    }
}
